﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace UnitTests.Framework
{
	internal class TestServer<T> : IDisposable where T:ClientHandlerBase, new()
	{
		#region Constants
		private const int MAX_BUFFER_SIZE = 32 * 1024; // 32Kb
		#endregion
		#region Fields
		private TcpListener _listener;
		private CancellationTokenSource _cancellationSource = new CancellationTokenSource();
		private Dictionary<TcpClient, T> _clients = new Dictionary<TcpClient, T>();
		private bool _disposed = false;
		#endregion
		#region Constructors
		/// <summary>
		/// Default Constructor
		/// </summary>
		/// <param name="tcpPort">Port the server should listen on</param>
		public TestServer(int tcpPort)
		{
			_listener = new TcpListener(System.Net.IPAddress.Loopback, tcpPort);
			_listener.Start();

			Task.Factory.StartNew(() => ClientConnectLoop(_cancellationSource.Token));
		}
		#endregion
		#region Properties
		/// <summary>
		/// Force all client to use encryption
		/// </summary>
		public bool UseImplicitEncryption { get; set; }
		#endregion
		#region Public Methods
		/// <summary>
		/// Finds the client handler associated with a particular client
		/// </summary>
		/// <param name="clientEndPoint">Client's socket endpoint (IPAddress, Port combo)</param>
		/// <returns>If found, the client hander associated with the client, otherwise null</returns>
		public T FindClientHandler(System.Net.EndPoint clientEndPoint)
		{
			var clientHandlers = from t in _clients
								 where ((t.Key != null) && (t.Key.Client.RemoteEndPoint.Equals(clientEndPoint)))
								 select t.Value;

			return clientHandlers.FirstOrDefault();
		}
		#endregion
		#region Private Methods
		private void ClientConnectLoop(CancellationToken cancel)
		{
			while (!cancel.IsCancellationRequested)
			{
				try
				{
					Task<TcpClient> getClientTask = _listener.AcceptTcpClientAsync();
					getClientTask.Wait(cancel);

					if (getClientTask.Status == TaskStatus.RanToCompletion)
						ClientHandlerLoop(getClientTask.Result, cancel);
				}
				catch (TaskCanceledException)
				{ }
			}
		}

		private async void ClientHandlerLoop(TcpClient client, CancellationToken cancel)
		{
			Stream stream = client.GetStream();
			StreamWriter streamWriter = new StreamWriter(stream, Encoding.ASCII);
			StreamReader streamReader = new StreamReader(stream, Encoding.ASCII);
			streamWriter.AutoFlush = true;
			T clientHandler = new T();
			_clients.Add(client, clientHandler);

			if (UseImplicitEncryption)
			{
				// TODO: Encryption
			}

			await streamWriter.WriteAsync(clientHandler.ConnectionMessage());

			while ((!cancel.IsCancellationRequested) && (!clientHandler.ShouldCloseConnection))
			{
				try
				{
					Task<string> readData = streamReader.ReadLineAsync();

					readData.Wait(cancel);

					if (readData.Status == TaskStatus.RanToCompletion)
					{
						string response = clientHandler.ProcessMessage(readData.Result);
						if (!string.IsNullOrEmpty(response))
							await streamWriter.WriteAsync(response);

						if (clientHandler.EncryptionStatus == EncryptionStatus.RequestExplicit)
						{
							// TODO: Encryption
						}
					}
				}
				catch (TaskCanceledException)
				{ }
			}

			if (stream != null)
				stream.Close();
			if (client != null)
			{
				_clients.Remove(client);
				client.Close();
			}			
		}
		#endregion
		#region IDisposable Members
		/// <summary>
		/// Disposes all managed and unmanaged resources
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Disposes all unmanaged resources and, optionally, the managed resources
		/// </summary>
		/// <param name="disposing">If true managed resources will be disposed</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!_disposed)
			{
				if (disposing)
				{
					_cancellationSource.Cancel();
					_listener.Stop();
				}

				// Unmanaged Resources
			}

			_disposed = true;
		}
		#endregion
	}

	/// <summary>
	/// Base implementation for client handlers
	/// </summary>
	internal abstract class ClientHandlerBase
	{
		/// <summary>
		/// Default constructor
		/// </summary>
		protected ClientHandlerBase()
		{
			ShouldCloseConnection = false;
			EncryptionStatus = Framework.EncryptionStatus.None;
		}

		/// <summary>
		/// Should the connection to the client be closed?
		/// </summary>
		public bool ShouldCloseConnection { get; protected set; }

		/// <summary>
		/// What is the current state of encryption on the connection
		/// </summary>
		public EncryptionStatus EncryptionStatus { get; protected set; }

		/// <summary>
		/// When overridden in a derived class, returns the message to send to the client when the client first connects
		/// </summary>
		/// <returns></returns>
		public abstract string ConnectionMessage();

		/// <summary>
		/// When overridden in a derived class, processes the message from the client and returns the message to send to the client
		/// </summary>
		/// <param name="receivedMessage"></param>
		/// <returns></returns>
		public abstract string ProcessMessage(string receivedMessage);
	}

	internal enum EncryptionStatus
	{
		None,
		Implicit,
		RequestExplicit,
		Explicit
	}
}
